from . import consts
from .mtoken import Token, TokenType, get_token_name

# scheme 关键字
keywords = {
    'define': TokenType.DEFINE,
    'lambda': TokenType.LAMBDA,
    'quote': TokenType.QUOTE,
    'if': TokenType.IF,
    'cond': TokenType.COND,
    'else': TokenType.ELSE,
    'begin': TokenType.BEGIN,
    'set!': TokenType.SET,
    '#f': TokenType.FALSE,
    '#t': TokenType.TRUE,
}


def is_digit(c):
    return '0' <= c <= '9'


def is_letter(c):
    return 'a' <= c <= 'z'


def is_sequence(c):
    '''
    <letter> | <digit>
    '''
    return is_initial(c) or is_digit(c)


def is_initial(c):
    '''
    <letter> | ! | $ | % | & | * | / | : | < | = | > | ? | ~ | _ | ^
    '''
    return c in '!$%&*+-/:<=>?~_^' or is_letter(c)


class Scanner:
    def __init__(self):
        self.source = None
        self.tokens = []
        self.start = 0
        self.current = 0
        self.offset = 0
        self.line = 1

    def debug(self):
        if consts.LEX:
            print("===================" + " token " + "===================")
            for token in self.tokens:
                print(f"<{get_token_name(token.ttype)},{token.value}>")
            print("==========================================")

    def scan_tokens(self, source) -> list[Token]:
        self.source = source
        while not self.is_at_end():
            self.start = self.current
            self.scan_token()
        self.tokens.append(Token('', TokenType.EOF, self.line, self.offset))
        tokens = self.tokens
        self.debug()
        return tokens

    def scan_token(self):
        c = self.advance()
        if c == '(':
            self.add_token(TokenType.LEFT_PAREN)
        elif c == ')':
            self.add_token(TokenType.RIGHT_PAREN)
        elif c == '{':
            self.add_token(TokenType.LEFT_BRACE)
        elif c == '}':
            self.add_token(TokenType.RIGHT_BRACE)
        elif c == ',':
            self.add_token(TokenType.COMMA)
        elif c == '-':
            self.add_token(TokenType.MINUS)
        elif c == '+':
            self.add_token(TokenType.PLUS)
        elif c == '.':
            self.add_token(TokenType.DOT)
        elif c == '/':
            self.add_token(TokenType.SLASH)
        elif c == '*':
            self.add_token(TokenType.STAR)
        elif c == '!':
            self.add_token(TokenType.BANG)
        elif c == '=':
            self.add_token(TokenType.EQUAL)
        elif c == '<':
            if self.match('='):
                self.add_token(TokenType.LESS_EQUAL)
            else:
                self.add_token(TokenType.LESS)
        elif c == '>':
            if self.match('='):
                self.add_token(TokenType.GREATER_EQUAL)
            else:
                self.add_token(TokenType.GREATER)
        elif c == ';':
            self.add_token(TokenType.SEMICOLON)
        elif c == '/':
            if self.match('/'):
                while self.peek() != '\n' and not self.is_at_end():
                    self.advance()
            else:
                self.add_token(TokenType.SLASH)
        elif c == ' ' or c == '\r' or c == '\t':
            pass
        elif c == '\n':
            self.line += 1
            self.offset = 0
        elif is_digit(c):
            self.number()
        elif c == '"':
            self.string()
        else:
            if is_initial(c):
                self.identifier()
            else:
                print(f'Unexpected character: {c}')

    def string(self):
        while self.peek() != '"' and not self.is_at_end():
            if self.peek() == '\n':
                self.line += 1
                self.offset = 0
            self.advance()
        if self.is_at_end():
            print('Unterminated string.')
            return
        self.advance()
        value = self.source[self.start + 1:self.current - 1]
        self.add_token(TokenType.STRING, literal=value)

    def number(self):
        while is_digit(self.peek()):
            self.advance()
        if self.peek() == '.' and is_digit(self.peek_next()):
            self.advance()
            while is_digit(self.peek()):
                self.advance()
        self.add_token(TokenType.NUMBER, float(self.source[self.start:self.current]))

    def peek_next(self):
        if self.current + 1 >= len(self.source):
            return '\0'
        return self.source[self.current + 1]

    def add_token(self, type: TokenType, literal=None):
        if literal is None:
            literal = self.source[self.start:self.current]
        self.tokens.append(Token(literal, type, self.line, self.offset))

    def peek(self):
        if self.is_at_end():
            return '\0'
        return self.source[self.current]

    def match(self, expected):
        if self.is_at_end():
            return False
        if self.source[self.current] != expected:
            return False
        self.current += 1
        self.offset += 1
        return True

    def advance(self):
        if self.is_at_end():
            return '\0'
        c = self.source[self.current]
        self.current += 1
        self.offset += 1
        return c

    def is_at_end(self):
        return self.current >= len(self.source)

    def identifier(self):
        while is_sequence(self.peek()):
            self.advance()
        text = self.source[self.start:self.current]
        if text in keywords:
            self.add_token(keywords[text])
        else:
            self.add_token(TokenType.IDENTIFIER)
        pass
